package org.budo.support.java.awt.robot;

import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Timestamp;
import java.util.Arrays;

import javax.swing.JFrame;
import javax.swing.JPanel;

import org.budo.support.java.awt.image.util.BufferedImageUtil;
import org.budo.support.javax.imageio.util.ImageIoUtil;
import org.budo.support.lang.util.IoUtil;

/**
 * @author limingwei
 */
public class BudoImageImpl implements BudoImage {
    private BufferedImage _bufferedImage;

    private Timestamp _createdAt;

    private BudoGraphics _graphics;

    private byte[] _png;

    public BudoImageImpl(BufferedImage bufferedImage) {
        if (null == bufferedImage) {
            throw new IllegalArgumentException("#33 bufferedImage = " + bufferedImage);
        }

        this._createdAt = new Timestamp(System.currentTimeMillis());
        this._bufferedImage = bufferedImage;
    }

    public BudoImageImpl(int width, int height) {
        this(width, height, BufferedImage.TYPE_BYTE_GRAY);
    }

    public BudoImageImpl(int width, int height, int imageType) {
        this(new BufferedImage(width, height, imageType));
    }

    public BudoImageImpl(InputStream inputStream) {
        this(ImageIoUtil.read(inputStream));
    }

    public BudoImageImpl(File file) {
        this(IoUtil.fileInputStream(file));
    }

    @Override
    public BudoImage drawString(String text, int x, int y) {
        return this.graphics() //
                .drawString(text, x, y) //
                .image();
    }

    @Override
    public int rgbMax() {
        int width = this.width();
        int height = this.height();

        int max = Integer.MIN_VALUE;

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int rgb = this.rgb(x, y);
                if (rgb > max) {
                    max = rgb;
                }
            }
        }

        return max;
    }

    @Override
    public int rgbMin() {
        int width = this.width();
        int height = this.height();

        int min = Integer.MAX_VALUE;

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int rgb = this.rgb(x, y);
                if (rgb < min) {
                    min = rgb;
                }
            }
        }

        return min;
    }

    @Override
    public int rgb(int x, int y) {
        try {
            BufferedImage bufferedImage = this.getBufferedImage();
            return bufferedImage.getRGB(x, y);
        } catch (Throwable e) {
            int w = this.width();
            int h = this.height();

            throw new RuntimeException("#78 x=" + x + ", y=" + y //
                    + ", w=" + w + ", h=" + h //
                    + ", e=" + e, e);
        }
    }

    @Override
    public int width() {
        BufferedImage bufferedImage = this.getBufferedImage();
        return bufferedImage.getWidth();
    }

    @Override
    public int height() {
        BufferedImage bufferedImage = this.getBufferedImage();
        return bufferedImage.getHeight();
    }

    @Override
    public BudoImage subImageVertical(int x, int w) {
        return this.subImage(x, 0, w, this.height());
    }

    @Override
    public BudoImage subImage(int x, int y, int w, int h) {
        BufferedImage bufferedImage = BufferedImageUtil.getSubimage(this.getBufferedImage(), x, y, w, h);
        return new BudoSubImageImpl(bufferedImage, this, x, y, w, h); // 返回子图
    }

    @Override
    public byte[] toPng() {
        if (null != this._png) {
            return this._png;
        }

        synchronized (BudoImageImpl.class) {
            if (null != this._png) {
                return this._png;
            }

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIoUtil.write(this.getBufferedImage(), "png", byteArrayOutputStream);
            this._png = byteArrayOutputStream.toByteArray();

            return this._png;
        }
    }

    @Override
    public void toPng(OutputStream outputStream) {
        IoUtil.write(this.toPng(), outputStream);
    }

    @Override
    public BudoGraphics graphics() {
        if (null != this._graphics) {
            return this._graphics;
        }

        synchronized (this) {
            if (null != this._graphics) {
                return this._graphics;
            }

            BufferedImage bufferedImage = this.getBufferedImage();
            Graphics graphics = bufferedImage.getGraphics();
            return this._graphics = new BudoGraphicsImpl(this, graphics);
        }
    }

    @Override
    public BudoImage subImage(Rectangle rectangle) {
        return this.subImage((int) rectangle.getX(), (int) rectangle.getY(), (int) rectangle.getWidth(), (int) rectangle.getHeight());
    }

    public BudoImage copy() {
        BufferedImage bufferedImage = this.getBufferedImage();
        BufferedImage bufferedImageNew = new BufferedImage(this.width(), this.height(), bufferedImage.getType());

        bufferedImage.copyData(bufferedImageNew.getRaster());
        return new BudoImageImpl(bufferedImageNew);
    }

    @Override
    public BufferedImage getBufferedImage() {
        return this._bufferedImage;
    }

    public void setBufferedImage(BufferedImage bufferedImage) {
        this._bufferedImage = bufferedImage;
    }

    @Override
    public BudoImage drawRect(int x, int y, int w, int h) {
        return this.graphics() //
                .drawRect(x, y, w, h) //
                .image();
    }

    @Override
    public BudoImage drawRect(Rectangle rectangle) {
        return this.graphics() //
                .drawRect(rectangle) //
                .image();
    }

    @Override
    public BudoImage drawCross(int x, int y) {
        return this.graphics() //
                .drawCross(x, y) //
                .image();
    }

    @Override
    public BudoImage drawImage(BudoImage image, int x, int y) {
        return this.graphics() //
                .drawImage(image, x, y) //
                .image();
    }

    @Override
    public BudoImage colorRandom() {
        return this.graphics() //
                .colorRandom() //
                .image();
    }

    public BudoImage lightGray() {
        return this.graphics() //
                .lightGray() //
                .image();
    }

    @Override
    public BudoImage orange() {
        return this.graphics() //
                .orange() //
                .image();
    }

    @Override
    public BudoImage pink() {
        return this.graphics() //
                .pink() //
                .image();
    }

    public BudoImage darkGray() {
        return this.graphics() //
                .darkGray() //
                .image();
    }

    @Override
    public BudoImage drawString(String text) {
        return this.graphics() //
                .drawString(text)//
                .image();
    }

    public BudoImage fillRect() {
        return this.graphics().fillRect().image();
    }

    @Override
    public BudoImage colorRange(int x, int y, int w, int h) {
        BudoImage sub = this.subImage(x, y, w, h);
        return this.drawString("[" + sub.rgbMin() + "," + sub.rgbMax() + "]", x + 2, y + 25);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(this.toPng());
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj) || //
                (null != obj //
                        && obj instanceof BudoImageImpl //
                        && Arrays.equals(this.toPng(), ((BudoImageImpl) obj).toPng()) //
                );
    }

    public void show() {
        JFrame frame = new JFrame("BudoImage");
        frame.setSize(100, 100);

        frame.getContentPane().add(new JPanel() {
            private static final long serialVersionUID = 1239245052268403084L;

            public void paint(Graphics graphics) {
                super.paint(graphics);
                graphics.drawImage(getBufferedImage(), 0, 0, null);
            }
        });
        frame.setVisible(true);
    }

    @Override
    public String toString() {
        return super.toString() //
                + ", bufferedImage.hash=" + this.getBufferedImage().hashCode() //
                + ", createdAt=" + this._createdAt //
                + ", width=" + this.width() //
                + ", height=" + this.height();
    }
}